package com.uxsino.simo.indicator.retractor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uxsino.commons.utils.config.ConfigProp;
import com.uxsino.commons.utils.config.PropElement;
import com.uxsino.reactorq.model.INDICATOR_TYPE;
import com.uxsino.simo.indicator.CompoundIndicator;
import com.uxsino.simo.indicator.IIndicatorField;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.networkentity.EntityInfo;
import com.uxsino.simo.query.QueryContext;
import com.uxsino.simo.query.QueryTemplate;
import com.uxsino.simo.utils.ConfigLoadingContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

public class DatasetKVRetractor extends AbstractValueRetractor<Map<String, Object>> {
    private static Logger logger = LoggerFactory.getLogger(DatasetKVRetractor.class);

    private CompoundIndicator targetIndicator;

    private static class _RetractorEntry {
        String originalName;

        SimpleValueRetractor retractor;
    }

    private Map<String, _RetractorEntry> fieldRetractors = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

    @ConfigProp(name = "key_field", required = true)
    private String keyFieldName;

    @ConfigProp(name = "value_field", required = true)
    private String valueFieldName;

    @ConfigProp(name = "recordset_num", required = false)
    private int datasetIndex = 0; // datasetIndex starts from 1, 0 to use all data set by
                                  // default

    public DatasetKVRetractor() {
        super(INDICATOR_TYPE.COMPOUND);
    }

    @Override
    public Object doRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {
        HashMap<String, Object> values = new HashMap<>();
        if (StringUtils.isAnyEmpty(keyFieldName, valueFieldName) || targetIndicator == null){
            return values;
        }
        ObjectMapper mapper = new ObjectMapper();
        MultiDataset md = mapper.convertValue(obj, new TypeReference<MultiDataset>(){});

        if (datasetIndex > 0 && md.size() < datasetIndex) {
            logger.error("recordset {} is required but onle {} recordset is returned", datasetIndex, md.size());
            return null;
        }

        if (datasetIndex <= 0) {
            for (int i = 0; i < md.size(); i++) {
                retractFromDataSet(entity, ctxt, qt, md.get(i), values);
            }
        } else {
            retractFromDataSet(entity, ctxt, qt, md.get(datasetIndex - 1), values);
        }
        return values;
    }

    private void retractFromDataSet(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Dataset nr, Map<String, Object> values) {
        int keyCol = getColumnIndex(nr, keyFieldName);
        int valueCol = getColumnIndex(nr, valueFieldName);

        if (keyCol < 0 || valueCol < 0) {
            return;
        }

        for (String[] row : nr.records) {

            _RetractorEntry entry = fieldRetractors.get(row[keyCol].trim());

            if (entry != null && entry.retractor != null) {
                values.put(entry.originalName, entry.retractor.retract(entity, ctxt, qt, row[valueCol]));
            }
        }
    }

    private static int getColumnIndex(Dataset nr, String fieldName) {
        int col_idx = nr.columnNames.getOrDefault(fieldName.trim(), 0) - 1;

        // not found in column names try if it an integer column index
        if (col_idx < 0) {
            try {
                col_idx = Integer.parseInt(fieldName.trim()) - 1;
            } catch (NumberFormatException e) {
                logger.error("column {} not found in dataset. column names in dataset: {}", fieldName, nr.columnNames);
                return -1;
            }
        }
        return col_idx;
    }

    public void setCompoundIndicator(Indicator ind) {
        if (!(ind instanceof CompoundIndicator)) {
            throw new IllegalArgumentException(ind.name + " " + " must be CompoundIndicator type");
        }
        targetIndicator = (CompoundIndicator) ind;
        fieldRetractors.clear();
        Iterator<Map.Entry<String, IIndicatorField>> it_fld = targetIndicator.getFieldIterator();

        while (it_fld.hasNext()) {
            IIndicatorField fld = it_fld.next().getValue();
            SimpleValueRetractor r = new SimpleValueRetractor(fld.getFieldType().caseIndType());
            _RetractorEntry entry = new _RetractorEntry();
            entry.originalName = fld.getName();
            entry.retractor = r;
            fieldRetractors.put(fld.getName(), entry);
        }
    }

    public void setKeyField(String keyField) {
        this.keyFieldName = keyField;
    }

    public void setDatasetIndex(int index) {
        this.datasetIndex = index;
    }

    public void setValueField(String valueField) {
        this.valueFieldName = valueField;
    }

    protected void setFieldValue(Map<String, Object> indValue, String fieldName, String fieldValue) {
        indValue.put(fieldName, fieldValue);
    }

    @Override
    public void loadProp(Indicator ind, PropElement eRetractor, ConfigLoadingContext lctxt) {
        /*TODO 解析器不限定指标类型,后续需要调整对应规则 if (!(ind instanceof CompoundIndicator)) {
            lctxt.error(eRetractor.getSourceLocation(), "{} must be CompoundIndicator type", ind.name);
            throw new IllegalArgumentException("must be CompoundIndicator type: " + ind.name);
        }*/
        setCompoundIndicator(ind);
        // kvr.setKeyField(eRetractor.getProp("key_field"));
        // kvr.setValueField(eRetractor.getProp("value_field"));
        // kvr.setDatasetIndex(eRetractor.getProp("recordset_num",0));

    }

    @Override
    public void addFieldExpr(String fieldName, EXPRTYPE exprType, String expr) {

    }

    @Override
    public void postRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {

    }
}
